[Amazon SageMaker] Amazon SageMaker Ground Truth で作成したデータをVoTT形式のデータに変換してみました
1 はじめに
CX事業本部の平内(SIN)です。
Amazon SageMaker Ground Truth (以下、Ground Truth)では、作業を完了したデータセットで、「少し(一部)だけ、修正したい」というような要求に対する仕組みは用意されていません。
今回は、物体検出(Bounding box)用に作成されたデータセットを、VoTT用に変換して、一部のアノテーションの修正・変更などが行えるようにしてみました。
※microsoft/VoTTは、マイクロソフトによって開発されているアプリケーションで、ローカルでアノテーションの定義が可能なツールです。
2 Ground Truthの出力
最初に、Ground Truthの出力である、画像とアノテーションデータ(output.manifest)をローカルにダウン ロードします。
output.manifestは、各行が各画像用のアノテーション情報であるJSONになっています。(以下、参考)
3 VoTTのプロジェクト作成
VoTT用に変換するためには、VoTT上での画像とアノテーション定義の関係を知る必要がありますが、この関係は、VoTTを起動しないと定義されないため、以下の手順で作業を進めます。
(1) Connection Settings
VoTTを起動し、Connection Settingsで、上でGround Truthの出力をダウンロードしたフォルダを定義します。
下図では、Ground Truthという名前で定義しています。Providerは、Local File Systemをし、先のフォルダが指定されています。
(2) プロジェクト作成
続いてNew Projectでプロジェクトを作成します。
プロジェクト名を指定(任意の名前)し、Source Connection及び、Target Coonnectionをともに、先に作成したGround Truthにします。
Save Projectでプロジェクトを作成すると、画像が読み込まれている事を確認できます(まだ、アノテーションは反映されていません)
サムネイルの画像を選択するごとに、オレンジ色のアイコンが追加されますので、全てのサムネイル画像を一回選択して下さい。このオレンジのアイコンが表示された時点で、画像とアドレーション設定の関連が生成されたことになります。
(3) ラベルの定義
画面右上のTAGSの+から、必要なラベルを定義します。(今回は、AHIRUとDOGに2つです)
(4) プロジェクトファイル
ここまでの作業を終えると、フォルダ内にVoTTのプロジェクトファイル(ここでは、GroundTruthData.vott)が増えている事を確認できます。
このプロジェクトファイルを開くと、以下のような構造となっており、assetsに、画像ファイル名ごとにidが振られていることがわかります。 このidから、アノテーション定義JSONのファイル名が決まります。
{id}-asset.json
{ "name": "GroundTruthData", //・・・(略) "sourceConnection": { "name": "GroundTruth", //・・・(略) }, "targetConnection": { "name": "GroundTruth", //・・・(略) }, "tags": [ { "name": "AHIRU", "color": "#5db300" }, { "name": "DOG", "color": "#e81123" } ], //・・・(略) "assets": { "cdd6e852fddb2ca802ffc5f851c2fde2": { "format": "jpg", "id": "cdd6e852fddb2ca802ffc5f851c2fde2", "name": "AHIRU-1586397259091391.jpg", "path": "file:/Users/xxxxxxxxxxxxxxxxxxxxxxxxxxxx/GroundTruth/AHIRU-1586397259091391.jpg", "size": { "width": 800, "height": 600 }, "state": 1, "type": 1 }, "821128a3e272a0ae103d8b04b005f97a": { "format": "jpg", "id": "821128a3e272a0ae103d8b04b005f97a", "name": "AHIRU-1586397378592848.jpg", "path": "file:/Users/xxxxxxxxxxxxxxxxxxxxxxxxxxxx/GroundTruth/AHIRU-1586397378592848.jpg", "size": { "width": 800, "height": 600 }, "state": 1, "type": 1 }, //・・・(略)
4 変換
下記のプログラムを実行することで、変換が行われます。設定が必要なのは、以下の2つです。
- targetPath 画像、output.minifest, VoTTのプロジェクトファイルが存在するフォルダ
- projectFile VoTTのプロジェクトファイル(ここでは、GroundTruthData.vott)
プログラムが行っているのは、output.manifestの内容を該当するVoTTのJSONファイルに反映しているだけです。
import json import glob import os import shutil import random, string # 定義 targetPath = '/Users/xxxxxxxxxx/GroundTruth' projectFile = 'GroundTruthData.vott' manifest = 'output.manifest' # 1件のデータを表現するクラス class Data(): def __init__(self, src): # プロジェクト名の取得 for key in src.keys(): index = key.rfind("-metadata") if(index!=-1): projectName = key[0:index] # メタデータの取得 metadata = src[projectName + '-metadata'] class_map = metadata["class-map"] # 画像名の取得 self.imgFileName = os.path.basename(src["source-ref"]) self.baseName = self.imgFileName.split('.')[0] # 画像サイズの取得 project = src[projectName] image_size = project["image_size"] self.img_width = image_size[0]["width"] self.img_height = image_size[0]["height"] self.annotations = [] # アノテーションの取得 for annotation in project["annotations"]: class_id = annotation["class_id"] top = annotation["top"] left = annotation["left"] width = annotation["width"] height = annotation["height"] self.annotations.append({ "label": class_map[str(class_id)], "width": width, "top": top, "height": height, "left": left }) # 全てのJSONデータを読み込む def getDataList(inputPath, manifest): dataList = [] with open("{}/{}".format(inputPath, manifest), 'r') as f: srcList = f.read().split('\n') for src in srcList: if(src != ''): json_src = json.loads(src) dataList.append(Data(json.loads(src))) return dataList # 画像名からデータを検索する def getData(dataList, imgName): for data in dataList: if(data.imgFileName == imgName): return data return None # ランダム文字列生成 def randomname(n): return ''.join(random.choices(string.ascii_letters + string.digits, k=n)) def main(): # 全てのJSONデータを読み込む dataList = getDataList(targetPath, manifest) log = "全データ: {}件 ".format(len(dataList)) # VoTTのプロジェクトファイルを読み込む with open("{}/{}".format(targetPath, projectFile), 'r') as f: project = json.loads(f.read()) assets = project["assets"] # プロジェクトからidの列挙 for id in assets.keys(): # 画像ファイル名の取得 imgName = assets[id]["name"] # 画像名からデータを検索する data = getData(dataList, imgName) # アノテーション情報 vott_json = {} vott_json["asset"] = { "format": "jpg", "id": id, "name": imgName, "path": "file:{}/{}".format(targetPath, imgName), "size": { "width": data.img_width, "height": data.img_height }, "state": 2, "type": 1 } vott_json["regions"] = [] for annotation in data.annotations: height = int(annotation["height"]) width = int(annotation["width"]) top = int(annotation["top"]) left = int(annotation["left"]) label = annotation["label"] vott_json["regions"].append({ "id": randomname(9), "type": "RECTANGLE", "tags": [ label ], "boundingBox": { "height": height, "width": width, "left": left, "top": top }, "points": [ { "x": left, "y": top }, { "x": left + width, "y": top }, { "x": left + width, "y": top+height }, { "x": left, "y": top + height } ] }) vott_json["version"] = "2.1.0" # jsonの保存 with open("{}/{}-asset.json".format(targetPath, id), mode='w') as f: json.dump(vott_json, f) main()
5 確認
変換が終わって、再びVoTTを開くと、アノテーションが反映されていることが確認できます。
統計情報で、ラベル数も確認できます。
6 最後に
今回は、Ground Truthで作成したデータセットをVoTTで利用可能なように変換してみました。
「オブジェクト検出」においては、なんだかんだ言っても、データセットを軽易に確認・修正出来る事が、結構大事だと感じています。ローカルで操作できるVoTTは、非常に強力だと思います。